
#include <QCoreApplication>
#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <QLibrary>

#include "config.h"

#include "pluginsmanager.h"
#include "abstractplugin/abstractplugin.h"
#include "abstractplugin/constants.h"

static PluginsManager *s_instance = nullptr;

typedef AbstractPlugin* (*PluginInstance)();

class PluginsManagerPrivate
{
public:
    PluginsManagerPrivate(PluginsManager *parent = nullptr);
    ~PluginsManagerPrivate();

    void load_all();
    void unload_all();
    bool load(const QString &pluginpath);
    bool load(AbstractPlugin* plugin);
    bool unload(AbstractPlugin* plugin);
    bool is_loaded(AbstractPlugin* plugin);
    bool is_valid(const QString &pluginfile);
    bool is_compatible(AbstractPlugin* plugin);
    QFunctionPointer resolve(const QString& pluginfile);
    AbstractPlugin *initialize(QFunctionPointer plugin);
    AbstractPlugin *initialize(const QString& pluginfile);

private:
    void update_plugin_paths();

public:
    QStringList pluginpaths;
    QList<AbstractPlugin*> plugins;
    AbstractPlugin *current_plugin;

private:
    PluginsManager * const q_ptr;
    Q_DECLARE_PUBLIC(PluginsManager)
};

PluginsManagerPrivate::PluginsManagerPrivate(PluginsManager *parent) : current_plugin(nullptr), q_ptr(parent)
{
    update_plugin_paths();
}

PluginsManagerPrivate::~PluginsManagerPrivate()
{
    unload_all();
}

void PluginsManagerPrivate::load_all()
{
#ifdef DEBUG_PLUGINSMGR
    qDebug().noquote() << "Plugins PathList:" << m_pluginpaths;
#endif
    for(const QString &pluginpath : qAsConst(pluginpaths)) {
        QDir dir(pluginpath);
        if(!dir.exists()) {
#ifdef DEBUG_PLUGINSMGR
            qDebug().noquote() << QString(QLatin1String("Plugin Path %1 not exists!")).arg(pluginpath);
#endif
            continue;
        }
        QFileInfoList entries = dir.entryInfoList(QStringList({QLatin1String("*")}), QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
        for(const QFileInfo &entry: qAsConst(entries)) {
#ifdef DEBUG_PLUGINSMGR
            qDebug().noquote() << "Detect Path:" << entry.filePath();
#endif
            if (entry.isDir()) {
                QDir subdir(entry.filePath());
                QFileInfoList files = subdir.entryInfoList(QStringList({QLatin1String("*.so")}), QDir::Files | QDir::NoDotAndDotDot);
                for(const QFileInfo &file: qAsConst(files)) {
                    load(file.filePath());
                }
            }
            if (entry.isFile() && entry.fileName().endsWith(QLatin1String(".so"))) {
                load(entry.filePath());
            }
        }
    }
    qDebug().noquote() << "Plugin load count:" << plugins.count();
}

void PluginsManagerPrivate::unload_all()
{
    while (!plugins.isEmpty()) {
        auto plugin = plugins.takeFirst();
        unload(plugin);
    }
}

bool PluginsManagerPrivate::load(const QString &pluginpath)
{
    QFileInfo fileinfo(pluginpath);
    if(!is_valid(fileinfo.filePath())) {
        qWarning().noquote() << QString(QLatin1String("File %1 is not a plugin!")).arg(fileinfo.fileName());
        return false;
    }
    return load(initialize(resolve(fileinfo.filePath())));
}

bool PluginsManagerPrivate::load(AbstractPlugin *plugin)
{
    if (!plugin || !is_compatible(plugin)) {
        qWarning().noquote() << "Plugin" << "[" << plugin->pluginName() << "]" << "loading failure";
        return false;
    }
    if (is_loaded(plugin)) {
        qWarning().noquote() << "Plugin" << "[" << plugin->pluginName() << "]" << "has been loaded";
        return true;
    }
    qDebug().noquote() << "Plugin" << "[" << plugin->pluginName() << "]" << "loaded successfully";
    plugins.append(plugin);
    Q_Q(PluginsManager);
    emit q->PluginLoaded(plugin->pluginName(), plugin->createWidget(), (plugin->order() == 0));
    return true;
}

bool PluginsManagerPrivate::unload(AbstractPlugin *plugin)
{
    if (plugin) {
        qDebug().noquote() << "Plugin unloading:" << "[" << plugin->pluginName() << "]";
        plugins.removeAll(plugin);
        plugin->deleteLater();
        plugin = nullptr;
    }
    return true;
}

bool PluginsManagerPrivate::is_loaded(AbstractPlugin *plugin)
{
    return plugins.contains(plugin);
}

bool PluginsManagerPrivate::is_valid(const QString &pluginfile)
{
    if (!QFileInfo::exists(pluginfile)) {
        qWarning().noquote() << QString(QLatin1String("File %1 not exist!")).arg(pluginfile);
        return false;
    }

    if (!QLibrary::isLibrary(pluginfile)) {
        qWarning().noquote() << QString(QLatin1String("File %1 is not a library!")).arg(pluginfile);
        return false;
    }

    if (!resolve(pluginfile)) {
        qWarning().noquote() << QString(QLatin1String("Plugin %1 resolve failed!")).arg(pluginfile);
        return false;
    }
    return true;
}

bool PluginsManagerPrivate::is_compatible(AbstractPlugin *plugin)
{
    // TODO: 需要更新版本判断逻辑
    QStringList pluginVer = plugin->minimalVerisonRequest().split(QLatin1String("."));
    QStringList frameVer = QString(QLatin1String(frameVerison)).split(QLatin1String("."));
    int verLength = pluginVer.count() < frameVer.count() ? pluginVer.count() : frameVer.count();
    for (int i=0; i<verLength; i++) {
        if (pluginVer[i] > frameVer[i]) {
            return false;
        }
    }
    if (pluginVer.count() > frameVer.count()) {
        return false;
    } else {
        return true;
    }
}

QFunctionPointer PluginsManagerPrivate::resolve(const QString &pluginfile)
{
    QFunctionPointer instance = nullptr;
    instance = QLibrary(pluginfile).resolve(PLUGIN_INSTANCE_FUNC);
    if (!instance) {
        instance = QLibrary(pluginfile).resolve(PLUGIN_INSTANCE_FUNC_DEV_COMPAT);
    }
    if (!instance) {
        return nullptr;
    }
    return instance;
}

AbstractPlugin *PluginsManagerPrivate::initialize(QFunctionPointer plugin)
{
    PluginInstance instance = (PluginInstance) plugin;
    if (!instance) {
        return nullptr;
    }
    return instance();
}

AbstractPlugin *PluginsManagerPrivate::initialize(const QString& pluginfile)
{
    return initialize(resolve(pluginfile));
}

void PluginsManagerPrivate::update_plugin_paths()
{
    pluginpaths.clear();
    QString envValue = QString::fromLocal8Bit(qgetenv(PLUGIN_PATH_ENV_NAME));
    if(!envValue.isEmpty()) {
        pluginpaths = envValue.split(QLatin1String(":"));
        pluginpaths.removeDuplicates();
    }
    QString plugindir = QString(QLatin1String("%1/plugins")).arg(qApp->applicationDirPath());
    if (!pluginpaths.contains(plugindir)) {
        pluginpaths.append(plugindir);
    }
    if (!pluginpaths.contains(APPLICATION_PLUGINS_PATH)) {
        pluginpaths.append(APPLICATION_PLUGINS_PATH);
    }
}

PluginsManager::PluginsManager(QObject *parent) : QObject(parent), d_ptr(new PluginsManagerPrivate(this))
{}

PluginsManager::~PluginsManager()
{}

QStringList PluginsManager::pluginpaths()
{ Q_D(PluginsManager); return d->pluginpaths; }

QList<AbstractPlugin*> PluginsManager::plugins()
{ Q_D(PluginsManager); return d->plugins; }

void PluginsManager::LoadAllPlugins()
{ Q_D(PluginsManager); d->load_all(); }

void PluginsManager::UnloadAllPlugins()
{ Q_D(PluginsManager); d->unload_all(); }

bool PluginsManager::SetCurrentPlugin(const QString &plugin_name)
{
    Q_D(PluginsManager);
    if (!d->current_plugin || d->current_plugin->pluginName() != plugin_name) {
        for (auto plugin : qAsConst(d->plugins)) {
            if (plugin->pluginName() == plugin_name) {
                d->current_plugin = plugin;
            }
        }
    }
    if (d->current_plugin && d->current_plugin->pluginName() == plugin_name) {
        return true;
    } else {
        return false;
    }
}

PluginsManager *PluginsManager::instance()
{
    if(s_instance) {
        return s_instance;
    } else {
        s_instance = new PluginsManager();
        return s_instance;
    }
}
